#include <stdint.h>   
#include <stdio.h>  
#include <io.h>
#include <process.h>  
#include <fcntl.h>  
#include <stdlib.h>  
#include <string.h> 



extern "C"{
//#include "x264.h" 
#include "libx264\x264.h"
#include <windows.h>
#include <winbase.h>
#include <mmsystem.h>
#pragma comment(lib, "winmm.lib ")
}


#define CLEAR(x) (memset((&x),0,sizeof(x)))  

#define IMAGE_WIDTH   1280  
#define IMAGE_HEIGHT  720 
#define FRAME_RATE  25

//char *read_filename="BigBuckBunny_640x360_static.yuv";  
char *read_filename="car_1280x720.yuv";  
//char *read_filename="testI420_1920x1080.yuv";
char *write_filename="x264yuv420p.h264"; 

static const int64_t kNumMillisecsPerSec = int64_t(1000);
static const int64_t kNumMicrosecsPerSec = int64_t(1000000);
static const int64_t kNumNanosecsPerSec = int64_t(1000000000);

static const int64_t kNumMicrosecsPerMillisec = kNumMicrosecsPerSec / kNumMillisecsPerSec;
static const int64_t kNumNanosecsPerMillisec = kNumNanosecsPerSec / kNumMillisecsPerSec;
static const int64_t kNumNanosecsPerMicrosec = kNumNanosecsPerSec / kNumMicrosecsPerSec;


int64_t SystemTimeNanos(void)
{
	int64_t ticks;
	static volatile long last_timegettime = 0;
	static volatile int64_t num_wrap_timegettime = 0;
	volatile long* last_timegettime_ptr = &last_timegettime;
	unsigned long now = timeGetTime();
	// Atomically update the last gotten time
	unsigned long old = InterlockedExchange(last_timegettime_ptr, now);
	if (now < old) {
		// If now is earlier than old, there may have been a race between threads.
		// 0x0fffffff ~3.1 days, the code will not take that long to execute
		// so it must have been a wrap around.
		if (old > 0xf0000000 && now < 0x0fffffff) {
			num_wrap_timegettime++;
		}
	}
	ticks = now + (num_wrap_timegettime << 32);
	// TODO(deadbeef): Calculate with nanosecond precision. Otherwise, we're
	// just wasting a multiply and divide when doing Time() on Windows.
	ticks = ticks * kNumNanosecsPerMillisec;
	return ticks;
}

int64_t SystemTimeMillis() {
	return static_cast<int64_t>(SystemTimeNanos() / kNumNanosecsPerMillisec);
}

static int encode_frame( x264_t *h, FILE* hout, x264_picture_t *pic, int64_t *last_dts )
{
	x264_picture_t pic_out;
	memset(&pic_out,0,sizeof(pic_out));
	x264_nal_t *nal=0;
	int i_nal=0;
	int i_frame_size = 0;

	i_frame_size = x264_encoder_encode( h, &nal, &i_nal, pic, &pic_out );

	if( i_frame_size < 0){
		printf("x264_encoder_encode failed\n" );
	}else if( i_frame_size >0)
	{
		//i_frame_size = fwrite( nal[0].p_payload, 1,i_frame_size,hout);
		//或者这样写
		int length=0;
		for (int i=0;i<i_nal;i++)
		{
			length+=fwrite( nal[i].p_payload, 1,nal[i].i_payload,hout);
		}
		i_frame_size = length;
		printf("the encode frame dts:%lld,pts:%lld,frame size:%d\n",pic_out.i_dts,pic_out.i_pts,length);
		*last_dts = pic_out.i_dts;
	}

	return i_frame_size;
}


int  main(int argc ,char **argv){
	uint8_t *yuv=NULL;
	yuv=(uint8_t *)malloc(IMAGE_WIDTH*IMAGE_HEIGHT*3/2);
	memset(yuv,0,IMAGE_WIDTH*IMAGE_HEIGHT*3/2);

	FILE *fd_read=NULL,*fd_write=NULL,*fd_time=NULL; 
	if((fd_read=fopen(read_filename,"rb"))==NULL){  
		printf("cannot open input file!\n");  
		exit(EXIT_FAILURE);  
	}  

	if((fd_write=fopen(write_filename,"wb"))==NULL){  
		printf("cannot open output file!\n");  
		exit(EXIT_FAILURE);  
	}
	if((fd_time=fopen("encode_time_car.txt","w"))==NULL){  
		printf("cannot open output time file!\n");  
		exit(EXIT_FAILURE);  
	}
	x264_t* m_h = NULL;
	x264_param_t m_param;
	//第一步先获取默认的编码器编码参数
	x264_param_default(&m_param);
	//* 使用默认参数，在这里因为我的是实时网络传输，所以我使用了zerolatency的选项，使用这个选项之后就不会有delayed_frames，如果你使用的不是这样的话，还需要在编码完成之后得到缓存的编码帧   
	x264_param_default_preset(&m_param, "ultrafast"/*"veryfast"*/, "zerolatency");  
	//限制输出文件的profile。这个参数将覆盖其它所有值，此选项能保证输出profile兼容的视频流。
	//如果使用了这个选项，将不能进行无损压缩。可选：baseline，main，high
	//x264_param_apply_profile(&m_param, "baseline");
	
	//* cpuFlags   
	m_param.i_threads  = X264_THREADS_AUTO/*X264_SYNC_LOOKAHEAD_AUTO*/;//* 取空缓冲区继续使用不死锁的保证.  
	m_param.i_log_level = X264_LOG_NONE; 
	//* 视频选项      
	m_param.i_width   = IMAGE_WIDTH; //* 要编码的图像宽度.   
	m_param.i_height  = IMAGE_HEIGHT; //* 要编码的图像高度   
	//m_param.i_frame_total = 0; //* 编码总帧数.不知道用0.

	/*设置最大参考帧数量，表示一帧最多允许参考多少其他的帧
	  会同时影响编码效率和解码效率，以及解码时缓存的数据量*/
	m_param.i_frame_reference = 1;//通常设置4-6 
	//完全关闭自适应I帧决策。
	//m_param.i_scenecut_threshold = 0;
	//设置x264输出中最大、最小的IDR帧（亦称关键帧）间距。
	//最大间距默认值（fps的10倍）对大多数视频都很好；最小间距与fps相等
	//设置gop max size;这个值越大的话，编码器灵活度越高，就不会被强迫插入IDR
	//当然这个值越大的话，对于本地存储体积会小，但是拖动进度条的时候压力也会越大
	//非直播实时场景的话，这个值建议250~900的范围
	m_param.i_keyint_max = 10; 
	//设置关键帧IDR间隔的最小值，
	//这个参数在编码器检测场景转换的时候，会插入I帧
	//在画面出现反复闪烁的时候，这个值能避免插入I帧
	m_param.i_keyint_min = 1;

	//* 流参数   
	//m_param.i_bframe  = 0; //设置两个参考帧之间的B帧数目,值越大，编码时间稍有提高

	//open-gop是一个提高效率的编码技术。有三种模式:none-停用open-gop;normal-启用open-gop;
	//bluray-启用open-gop。一个效率较低的open-gop版本，因为normal模式无法用于蓝光编码.
	//某些解码器不完全支援open-gop资料流，这就是为什么此选项并未默认为启用。
	//m_param.b_open_gop  = 0;
	//m_param.b_open_gop = 1;
	
	
	/*设置B帧参考其他帧的方式;0:关闭，1为了BD原盘播放兼容,2为正常模式，不加限制*/
	//m_param.i_bframe_pyramid = 0;
	/*采用什么算法来决定是否采用B帧
		0:关闭；1:采用快速算法；2:常规算法，比较优秀，但速度方面比较慢*/
	//m_param.i_bframe_adaptive = 0;//采用什么算法来决定是否采用B帧 

	//* Log参数，不需要打印编码信息时直接注释掉就行   
	//m_param.i_log_level  = X264_LOG_DEBUG;  
  
	//* muxing parameters   
	m_param.i_fps_den  = 1; //* 帧率分母   
	m_param.i_fps_num  = FRAME_RATE;//* 帧率分子   
// 	m_param.i_timebase_den = m_param.i_fps_num;  
// 	m_param.i_timebase_num = m_param.i_fps_den;  

	/*  deblocking功能主要是去色块，因为编码是分macroblock宏块编码的，
		编码以后，块之间可能会出现分界线，这个deblocking滤波器主要就是消除它们的，
		一般来说，码率越高的话，越建议调低alpha和beta的值，这个值越高，他的强度会加大，就会更加模糊*/
	//完全关闭内置去块滤镜，不推荐使用。
	//调节H.264标准中的内置去块滤镜。这是个性价比很高的选则, 关闭.
	//m_param.b_deblocking_filter = 1;
	//m_param.i_deblocking_filter_alphac0 = 0;
	//m_param.i_deblocking_filter_beta = 0;

	//停用弹性内容的二进制算数编码（CABAC：Context Adaptive Binary Arithmetic Coder）资料流压缩，
	//切换回效率较低的弹性内容的可变长度编码（CAVLC：Context Adaptive Variable Length Coder）系统。
	//大幅降低压缩效率（通常10~20%）和解码的硬件需求。
	//使用网格编码量化以增进编码效率：0-关闭, 1-仅在宏块最终编码时启用, 2-所有模式下均启用.
	//选项1提供了速度和效率间较好的均衡，选项2大幅降低速度.
	//注意：需要开启 –cabac选项生效.
	//m_param.b_cabac = 1;//一般情况开启，使用CABAC
	//m_param.i_cabac_init_idc = 0;

	/*analyse:编码分析参数*/
	//i_luma_deadzone[0]和i_luma_deadzone[1]分别对应inter和intra，取值范围1~32
	//测试可以得知，这连个参数的调整可以对数据量有很大影响，值越大数据量相应越少，占用带宽越低.
	//m_param.analyse.i_luma_deadzone[0] = 32;
	//m_param.analyse.i_luma_deadzone[1] = 32;
	//为’direct’类型的运动矢量设定预测模式。有两种可选的模式：spatial（空间预测）和temporal（时间预测）。默认：’spatial’
	//可以设置为’none’关闭预测，也可以设置为’auto’让x264去选择它认为更好的模式，x264会在编码结束时告诉你它的选择。
	//m_param.analyse.i_direct_mv_pred = X264_DIRECT_PRED_NONE;

	//开启明确的权重预测以增进P帧压缩。越高级的模式越耗时，有以下模式：
	//0 : 关闭; 1 : 静态补偿（永远为-1）; 2 : 智能统计静态帧，特别为增进淡入淡出效果的压缩率而设计
	//m_param.analyse.i_weighted_pred = X264_WEIGHTP_NONE;

	//设置全局的运动预测方法，有以下5种选择：dia（菱形搜索，速度最快）, hex（六边形搜索）, umh（不均匀的多六边形搜索）
	//esa（全局搜索），tesa（变换全局搜索），默认：’hex’
	//m_param.analyse.i_me_method = X264_ME_DIA;

	//merange控制运动搜索的最大像素范围。对于hex和dia，范围被控制在4-16像素，默认就是16。
	//对于umh和esa，可以超过默认的 16像素进行大范围的运行搜索，这对高分辨率视频和快速运动视频而言很有用。
	//注意，对于umh、esa、tesa，增大merange会显著地增加编码耗时。默认：16
	//m_param.analyse.i_me_range = 4;

	//设置亚像素估计的复杂度。值越高越好。级别1-5简单控制亚像素的细化力度。级别6给模式决策开启RDO（码率失真优化模式），
	//级别8给运动矢量和帧内预测模式开启RDO。开启RDO会显著增加耗时。
	//使用小于2的值会开启一个快速的、低质量的预测模式，效果如同设置了一个很小的 –scenecut值。不推荐这样设置。
	//m_param.analyse.i_subpel_refine = 1;

	//Mixed refs（混合参照）会以8×8的切块为参照取代以整个宏块为参照。会增进多帧参照的帧的质量，会有一些时间耗用.
	//m_param.analyse.b_mixed_references = 0;

	//通常运动估计都会同时考虑亮度和色度因素。开启此选项将会忽略色度因素换取一些速度的提升。
	//m_param.analyse.b_chroma_me = 1;
	//m_param.analyse.i_trellis = 1;

	//关闭或启动为了心理视觉而降低psnr或ssim的优化。此选项同时也会关闭一些不能通过x264命令行设置的内部的心理视觉优化方法。
	//m_param.analyse.b_psy = 0;

	//关闭P帧的早期跳过决策。大量的时耗换回非常小的质量提升。
	//m_param.analyse.b_fast_pskip = 0;//高速编码需要关闭

	//DCT抽样会丢弃看上去“多余”的DCT块。会增加编码效率，通常质量损失可以忽略。
	//m_param.analyse.b_dct_decimate = 1;
	//去掉信噪比的计算，因为在解码端也可用到.
	//m_param.analyse.b_psnr = 0; //是否使用信噪比.

	/* rc:码率控制参数 */
	//码率控制模式有ABR（平均码率）、CQP（恒定质量）、CRF（恒定码率）.
	//ABR模式下调整i_bitrate，CQP下调整i_qp_constant调整QP值，范围0~51，值越大图像越模糊，默认23.
	//太细致了人眼也分辨不出来，为了增加编码速度降低数据量还是设大些好,CRF下调整f_rf_constant和f_rf_constant_max影响编码速度和图像质量（数据量）；
	//m_param.rc.i_rc_method = X264_RC_CQP;
	//m_param.rc.i_qp_constant = 30;
	//设置码率
	m_param.rc.i_bitrate = 1*1000 * 1000;//* 码率(比特率,单位Kbps)
	//vbv的参数设置主要是用于限制峰值，对视频体积和平均码率不应造成很大的影响，这两个参数的值只有在VBR和ABR的情况下，才有意义
	//通常很多情况下设置为vbv-maxrate = vbv-bufsize = bitrate，这里注意vbv-bufsize的量纲是不同，即最大缓存1s的数据
	/*这里说明一下：
	在RC_ABR码率控制方法下，如果vbv-maxrate == bitrate这时候其实进行的是CBR码率控制方法，encoder力争控制每一帧输出都稳定在bitrate上。
	如果设置vbv-maxrate则vbv-bufsize必须设置，否则会提示"VBV maxrate specified, but no bufsize, ignored"， vbv-maxrate会被改回为0。
	如果设置vbv-bufsize而vbv-maxrate没有设置，则会提示"VBV maxrate unspecified, assuming CBR"，vbv-maxrate会被设为bitrate，此时进行CBR编码。
	一般设置为 vbv-maxrate = vbv-bufsize = a*bitrate。
	a=0，不启用VBV机制，编码性能最好，适用于硬盘文件编码；但它输出的码率波动性大，有可能某些帧的比特数过高，不适用于有实际带宽限制的流媒体传输。
	0<a<1，这样的设置没有什么意义，得不到任何的好处。
	a=1，等于CBR，CBR是一种复杂和平滑场景都不大讨好的码率控制方法，一般不采用这一方法。
	a>1，对每帧数据有限制，但又可以暂时超过平均码率，适用于流媒体传输。
	对于某些特殊场景编码，例如电脑屏幕编码，它的特点是I帧纹理细节丰富，编码数据极大，P帧变化很小或者根本没变化，p帧数据很小。
	这种情况，如果不设置vbv参数（保持缺省值0），I帧数据压不下来，码流会周期性的高低变动；有可能造成网络拥塞，数据包丢失，解码端花屏。
	如果设置a=1，I帧得到限制，可以压下来，但编码质量下降太多，主观质量差。
	因此需要根据网络设置成a>1，使得I帧可以暂时有限度的大于平均码率，而P帧编码时候还能把平均码率降下来。
	原文链接：https://blog.csdn.net/huibailingyu/article/details/43763181
	*/
	//m_param.rc.i_vbv_max_bitrate= 1*1000*1000;//设置最大码率，避免瞬时码率峰值过高，通常这个值设置比bufsize低一点，例如=bufsize-(bufsize/10) 
	//m_param.rc.i_vbv_buffer_size= 1*1000*1000;  //设置码率值，避免码率暴增的现象

	//为mb-tree ratecontrol（Macroblock Tree Ratecontrol）和vbv-lookahead设置可用的帧的数量。最大可设置为250。
	//对于mb-tree而言，调大这个值(i_lookahead)会得到更准确地结果，但也会更慢。
	//mb-tree能使用的最大值是–rc-lookahead和–keyint中较小的那一个。
	//m_param.rc.b_mb_tree = 1;//开启mb-tree
	//m_param.rc.i_lookahead = 0; //决定mbtree检索的帧数，如果需要在低延时环境下(直播)建议设置为fps的4/5的值，一般场景设置为fps的2~3倍
	
	//自适应量化器模式。不使用自适应量化的话，x264趋向于使用较少的bit在缺乏细节的场景里。
	//自适应量化可以在整个视频的宏块里更好地分配比特。它有以下选项：
	//0-完全关闭自适应量化器;1-允许自适应量化器在所有视频帧内部分配比特;2-根据前一帧强度决策的自变量化器（实验性的）。默认值=1
	//m_param.rc.i_aq_mode = 0;
	//m_param.rc.f_aq_strength = 0.8;//自适应量化强度，在aq_mode=1的情况下建议0.8；aq_mode=1建议0.9；aq_mode=3建议0.7	
	//记录用时
	int64_t start_time = SystemTimeMillis();
	int64_t last_time=start_time;
	//打开编码器
	/* 根据输入参数param初始化总结构 x264_t */
	if( ( m_h = x264_encoder_open( &m_param ) ) == NULL )
	{
		 fprintf( stderr, "x264 [error]: x264_encoder_open failed\n" );
		 return -1;
	}
	
	//开始编码数据
	do 
	{
		if( !m_param.b_repeat_headers )
		{
			// Write SPS/PPS/SEI
			x264_nal_t *headers=NULL;
			int i_nal;

			if(x264_encoder_headers( m_h, &headers, &i_nal ) < 0){
				printf("x264_encoder_headers failed\n" );
				break;
			}
			//
			for (int i=0;i<i_nal;i++)
			{
				//fwrite(headers[i].p_payload,1,headers[i].i_payload,fd_write);
				if (fwrite(headers[i].p_payload,1,headers[i].i_payload,fd_write)<0)
				{
					printf("error writing headers to output file\n" );
					exit(EXIT_FAILURE);
				}
			}
				
		}
		int64_t last_dts=0;
		int i_frame_size=0;
		x264_picture_t m_pic;
		x264_picture_init(&m_pic);
		m_pic.img.i_csp = X264_CSP_I420;
		m_pic.img.i_plane=3;
		m_pic.img.plane[0]=yuv;  
		m_pic.img.i_stride[0]=IMAGE_WIDTH;
		m_pic.img.plane[1]=yuv+IMAGE_WIDTH*IMAGE_HEIGHT;  
		m_pic.img.i_stride[1]=IMAGE_WIDTH/2;
		m_pic.img.plane[2]=yuv+IMAGE_WIDTH*IMAGE_HEIGHT+IMAGE_WIDTH*IMAGE_HEIGHT/4; 
		m_pic.img.i_stride[2]=IMAGE_WIDTH/2;

		while(fread(yuv,1,IMAGE_WIDTH*IMAGE_HEIGHT*3/2,fd_read)>0){ 			
			i_frame_size=encode_frame(m_h,fd_write,&m_pic,&last_dts);
			if (i_frame_size<0){
				break;
			}
			if (i_frame_size>0)
			{
				int64_t cur_time = SystemTimeMillis();
				int64_t spend=cur_time-last_time;
				last_time = cur_time;
				printf("encode frame,spend time:%lld ms\n",spend);
				char timebuf[512]={0};
				sprintf(timebuf,"encode frame,spend time:%lld ms\n",spend);
				fwrite(timebuf,1,strlen(timebuf),fd_time);
			}
			//将pts自增
			m_pic.i_pts++;  
		}  
		//这个时候，如果存在延时编码帧的话，需要刷新编码器的延时帧
		 while(x264_encoder_delayed_frames( m_h )>0){
			 i_frame_size = encode_frame( m_h, fd_write, NULL, &last_dts );
			 if (i_frame_size<0){
				 break;
			 }
			 if (i_frame_size>0)
			 {
				 int64_t cur_time = SystemTimeMillis();
				 int64_t spend=cur_time-last_time;
				 last_time = cur_time;
				 printf("encode frame,spend time:%lld ms\n",spend);
				 char timebuf[512]={0};
				 sprintf(timebuf,"encode frame,spend time:%lld ms\n",spend);
				 fwrite(timebuf,1,strlen(timebuf),fd_time);
			 }
		 }
	} while (0);

	int64_t cur_time = SystemTimeMillis();
	int64_t spend_total=cur_time-start_time;
	printf("encode frame,total spend time:%lld ms\n",spend_total);
	char timebuf[512]={0};
	sprintf(timebuf,"encode frame,total spend time:%lld  ms\n",spend_total);
	fwrite(timebuf,1,strlen(timebuf),fd_time);

	if (yuv)
	{
		free(yuv);  
		yuv=NULL;
	}
	
	if (m_h)
	{
		x264_encoder_close(m_h);   
		m_h=NULL;
	}
	if (fd_read)
	{
		fclose(fd_read);  
		fd_read=NULL;
	}
	if (fd_write)
	{
		fclose(fd_write);  
		fd_write=NULL;
	}
	system("pause");
	return 0;
}
